Dynamically allocate soinfo-structs in linker

Request memory from the system when needed instead of having a fixed
array for soinfo structs. Note that malloc() et al can't be used in
linker, so use mmap() instead.

Change-Id: I4b495995931d7752b0e8c944e64d1fe41b9f7144
diff --git a/linker/linker.cpp b/linker/linker.cpp
index 479d4b9..f7f8125 100644
--- a/linker/linker.cpp
+++ b/linker/linker.cpp
@@ -51,8 +51,6 @@
 #include "linker_format.h"
 #include "linker_phdr.h"
 
-#define SO_MAX 128
-
 /* Assume average path length of 64 and max 8 paths */
 #define LDPATH_BUFSIZE 512
 #define LDPATH_MAX 8
@@ -76,16 +74,22 @@
  *   and NOEXEC
  * - linker hardcodes PAGE_SIZE and PAGE_MASK because the kernel
  *   headers provide versions that are negative...
- * - allocate space for soinfo structs dynamically instead of
- *   having a hard limit (SO_MAX)
  */
 
 
 static bool soinfo_link_image(soinfo* si);
 
-static int socount = 0;
-static soinfo sopool[SO_MAX];
-static soinfo *freelist = NULL;
+// We can't use malloc(3) in the dynamic linker. We use a linked list of anonymous
+// maps, each a single page in size. The pages are broken up into as many struct soinfo
+// objects as will fit, and they're all threaded together on a free list.
+#define SOINFO_PER_POOL ((PAGE_SIZE - sizeof(soinfo_pool_t*)) / sizeof(soinfo))
+struct soinfo_pool_t {
+  soinfo_pool_t* next;
+  soinfo info[SOINFO_PER_POOL];
+};
+static struct soinfo_pool_t* gSoInfoPools = NULL;
+static soinfo* gSoInfoFreeList = NULL;
+
 static soinfo *solist = &libdl_info;
 static soinfo *sonext = &libdl_info;
 static soinfo *somain; /* main process, always the one after libdl_info */
@@ -263,38 +267,57 @@
     rtld_db_dlactivity();
 }
 
-static soinfo *soinfo_alloc(const char *name)
-{
-    if (strlen(name) >= SOINFO_NAME_LEN) {
-        DL_ERR("library name \"%s\" too long", name);
-        return NULL;
-    }
+static bool ensure_free_list_non_empty() {
+  if (gSoInfoFreeList != NULL) {
+    return true;
+  }
 
-    /* The freelist is populated when we call soinfo_free(), which in turn is
-       done only by dlclose(), which is not likely to be used.
-    */
-    if (!freelist) {
-        if (socount == SO_MAX) {
-            DL_ERR("too many libraries when loading \"%s\"", name);
-            return NULL;
-        }
-        freelist = sopool + socount++;
-        freelist->next = NULL;
-    }
+  // Allocate a new pool.
+  soinfo_pool_t* pool = reinterpret_cast<soinfo_pool_t*>(mmap(NULL, sizeof(*pool),
+                                                              PROT_READ|PROT_WRITE,
+                                                              MAP_PRIVATE|MAP_ANONYMOUS, 0, 0));
+  if (pool == MAP_FAILED) {
+    return false;
+  }
 
-    soinfo* si = freelist;
-    freelist = freelist->next;
+  // Add the pool to our list of pools.
+  pool->next = gSoInfoPools;
+  gSoInfoPools = pool;
 
-    /* Make sure we get a clean block of soinfo */
-    memset(si, 0, sizeof(soinfo));
-    strlcpy((char*) si->name, name, sizeof(si->name));
-    sonext->next = si;
-    si->next = NULL;
-    si->refcount = 0;
-    sonext = si;
+  // Chain the entries in the new pool onto the free list.
+  gSoInfoFreeList = &pool->info[0];
+  soinfo* next = NULL;
+  for (int i = SOINFO_PER_POOL - 1; i >= 0; --i) {
+    pool->info[i].next = next;
+    next = &pool->info[i];
+  }
 
-    TRACE("%5d name %s: allocated soinfo @ %p\n", pid, name, si);
-    return si;
+  return true;
+}
+
+static soinfo* soinfo_alloc(const char* name) {
+  if (strlen(name) >= SOINFO_NAME_LEN) {
+    DL_ERR("library name \"%s\" too long", name);
+    return NULL;
+  }
+
+  if (!ensure_free_list_non_empty()) {
+    DL_ERR("out of memory when loading \"%s\"", name);
+    return NULL;
+  }
+
+  // Take the head element off the free list.
+  soinfo* si = gSoInfoFreeList;
+  gSoInfoFreeList = gSoInfoFreeList->next;
+
+  // Initialize the new element.
+  memset(si, 0, sizeof(soinfo));
+  strlcpy(si->name, name, sizeof(si->name));
+  sonext->next = si;
+  sonext = si;
+
+  TRACE("%5d name %s: allocated soinfo @ %p\n", pid, name, si);
+  return si;
 }
 
 static void soinfo_free(soinfo* si)
@@ -323,8 +346,8 @@
     */
     prev->next = si->next;
     if (si == sonext) sonext = prev;
-    si->next = freelist;
-    freelist = si;
+    si->next = gSoInfoFreeList;
+    gSoInfoFreeList = si;
 }
 
 #ifdef ANDROID_ARM_LINKER
diff --git a/linker/linker.h b/linker/linker.h
index cb33602..de83a8c 100644
--- a/linker/linker.h
+++ b/linker/linker.h
@@ -165,7 +165,6 @@
     bool has_DT_SYMBOLIC;
 };
 
-
 extern soinfo libdl_info;